package com.gaff.emp.core.modules.auth.service.impl;

import cn.hutool.core.util.StrUtil;
import com.gaff.emp.core.EmpException;
import com.gaff.emp.core.modules.auth.JwtUtil;
import com.gaff.emp.core.modules.auth.LoginUserInfo;
import com.gaff.emp.core.modules.auth.callback.LoginCallback;
import com.gaff.emp.core.modules.auth.form.LoginForm;
import com.gaff.emp.core.modules.auth.form.SsoLoginForm;
import com.gaff.emp.core.modules.auth.form.SsoPasswordForm;
import com.gaff.emp.core.modules.auth.rest.AuthForm;
import com.gaff.emp.core.modules.auth.rest.AuthResponse;
import com.gaff.emp.core.modules.auth.rest.BaseResponse;
import com.gaff.emp.core.modules.auth.rest.PasswordForm;
import com.gaff.emp.core.modules.auth.service.AuthService;
import com.gaff.emp.core.modules.perm.entity.SysMenu;
import com.gaff.emp.core.modules.perm.entity.SysRole;
import com.gaff.emp.core.modules.perm.entity.SysUser;
import com.gaff.emp.core.modules.perm.service.SysRoleService;
import com.gaff.emp.core.modules.perm.service.SysUserService;
import com.gaff.emp.core.modules.sys.annotation.Dict;
import com.gaff.emp.core.util.HttpUtil;
import com.google.code.kaptcha.Producer;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.awt.image.BufferedImage;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AuthServiceImpl implements AuthService, BeanPostProcessor, InitializingBean {

    private Cache<String, String> captchaCache = null;

    private Cache<String, Integer> lockedCache = null;

    @Value("${gaff.lock-enable:false}")
    private Boolean lockEnable;

    @Value("${gaff.lock-count:5}")
    private Integer lockCount;

    @Value("${gaff.lock-count:300}")
    private Integer lockTime;

    @Autowired
    private Producer producer;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysRoleService sysRoleService;

    @Autowired
    private RestTemplate restTemplate;

    private LoginCallback callback;

    @Override
    public BufferedImage getCaptcha(String uuid) {
        String code = producer.createText();
        BufferedImage bufferedImage = producer.createImage(code);
        captchaCache.put(uuid, code);
        return bufferedImage;
    }

    @Override
    public Map<String, Object> login(LoginForm form) {
        if (lockEnable) {
            Integer failedCount = lockedCache.getIfPresent(form.getUsername());
            if (failedCount != null && failedCount >= lockCount) {
                throw new EmpException("连续多次账号或密码不正确，请稍后重试");
            }
        }

        if (!form.getCaptcha().equalsIgnoreCase(captchaCache.getIfPresent(form.getUuid()))) {
            throw new EmpException("验证码不正确");
        }
        SysUser sysUser = sysUserService.queryByUserName(form.getUsername());

        // 账号不存在、密码错误
        if (sysUser == null || !sysUser.getPassword().equals(form.getPassword())) {
            if (lockEnable) {
                Integer failedCount = lockedCache.getIfPresent(form.getUsername());
                if (failedCount == null) {
                    failedCount = 0;
                }
                lockedCache.put(form.getUsername(), failedCount + 1);
            }
            throw new EmpException("账号或密码不正确");
        }

        if (lockEnable) {
            lockedCache.invalidate(form.getUsername());
        }

        // 账号锁定
        if (sysUser.getStatus() == SysUser.locked) {
            throw new EmpException("账号已被锁定,请联系管理员");
        }

        sysUser.setPassword("");

        SysRole role = sysRoleService.queryById(sysUser.getRoleId());

        List<SysMenu> menus = sysRoleService.queryMenusByRoleId(sysUser.getRoleId());

        Object callbackResult = null;
        if (callback != null) {
            callbackResult = callback.doLogin(sysUser, menus);
        }

        return buildResult(sysUser, role, menus, callbackResult);
    }

    @Override
    public void logout(LoginUserInfo userInfo) {
        JwtUtil.clearUserInfo(userInfo);
    }

    @Override
    public Map<String, Object> ssoLogin(SsoLoginForm form) {
        String casUrl = form.getSsoCasUrl();
        Map<String, String> params = new HashMap<>();
        params.put("ticket", form.getTicket());
        params.put("service", form.getSsoAppUrl());
        String fullUrl = HttpUtil.appendParamToUrl(casUrl + "/serviceValidate", params);
        log.info("cas ticket validation full url = " + fullUrl);
        ResponseEntity<String> casResponseEntity = restTemplate.getForEntity(fullUrl, String.class);
        if (casResponseEntity.getStatusCode() != HttpStatus.OK) {
            throw new EmpException("cas server validation ticket error");
        }
        String casResponse = casResponseEntity.getBody();
        if (casResponse.contains("cas:authenticationFailure")) {
            log.warn(casResponse);
            throw new EmpException(HttpStatus.UNAUTHORIZED.value(), casResponse);
        }
        log.info(casResponse);
        String username = casResponse.substring(casResponse.indexOf("<cas:user>") + 10, casResponse.indexOf("</cas:user>"));

        AuthForm authForm = new AuthForm();
        authForm.setAppName(form.getSsoAppName());
        authForm.setUsername(username);
        ResponseEntity<AuthResponse> authResponseEntity = restTemplate.postForEntity(form.getSsoMenuUrl() + "/auth", authForm, AuthResponse.class);
        if (authResponseEntity.getStatusCode() != HttpStatus.OK) {
            throw new EmpException("menu server auth error");
        }
        AuthResponse authResponse = authResponseEntity.getBody();
        if (authResponse.getCode() != HttpStatus.OK.value()) {
            throw new EmpException("menu server auth error, " + authResponse.getMsg());
        }


        SysUser user = authResponse.getData().getUser();
        SysRole role = authResponse.getData().getRole();
        List<SysMenu> menus = authResponse.getData().getMenus();
        Object callbackResult = null;
        if (callback != null) {
            callbackResult = callback.doLogin(user, menus);
        }

        return buildResult(user, role, menus, callbackResult);
    }

    @Override
    public void ssoPassword(LoginUserInfo userInfo, SsoPasswordForm form) {
        PasswordForm passwordForm = new PasswordForm();
        passwordForm.setUsername(userInfo.getUser().getUsername());
        passwordForm.setPassword(form.getPassword());
        passwordForm.setNewPassword(form.getNewPassword());
        ResponseEntity<BaseResponse> responseEntity = restTemplate.postForEntity(form.getSsoMenuUrl() + "/updatePassword", passwordForm, BaseResponse.class);
        if (responseEntity.getStatusCode() != HttpStatus.OK) {
            throw new EmpException("menu server auth error");
        }
        if (responseEntity.getBody().getCode() != HttpStatus.OK.value()) {
            throw new EmpException("menu server auth error, " + responseEntity.getBody().getMsg());
        }
        JwtUtil.clearUserInfo(userInfo);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof LoginCallback) {
            this.callback = (LoginCallback) bean;
        }
        return bean;
    }

    private Map<String, Object> buildResult(SysUser user, SysRole role, List<SysMenu> menus, Object callbackResult) {
        LoginUserInfo loginUserInfo = new LoginUserInfo();
        loginUserInfo.setUser(user);
        loginUserInfo.setRole(role);
        loginUserInfo.setMenus(menus);
        loginUserInfo.setExtra(callbackResult);

        Map<String, Object> result = new HashMap<>();
        result.put("token", JwtUtil.generateToken(loginUserInfo));
        result.put("userInfo", loginUserInfo);
        return result;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        captchaCache = CacheBuilder.newBuilder()
                .initialCapacity(1000)
                .concurrencyLevel(Runtime.getRuntime().availableProcessors())
                .expireAfterWrite(5 * 60, TimeUnit.SECONDS)
                .build();
        lockedCache = CacheBuilder.newBuilder()
                .initialCapacity(1000)
                .concurrencyLevel(Runtime.getRuntime().availableProcessors())
                .expireAfterWrite(lockTime, TimeUnit.SECONDS)
                .build();
    }

    private void checkAccountFailed(String username) {

    }

}
